package cn.whale.kill.service.impl;

import cn.whale.basic.utils.AssertUtil;
import cn.whale.basic.utils.DateTimeUtil;
import cn.whale.basic.utils.IdWorker;
import cn.whale.kill.domain.KillCourse;
import cn.whale.kill.domain.KillSessions;
import cn.whale.kill.dto.KillParamsDto;
import cn.whale.kill.mapper.KillCourseMapper;
import cn.whale.kill.mq.callback.KillMsgConfirmCallback;
import cn.whale.kill.service.IKillCourseService;
import cn.whale.kill.to.CourseKillTo;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import static cn.whale.basic.constants.MQConstants.EXCHNAGE_ORDER_KILL;
import static cn.whale.basic.constants.MQConstants.ROUTINGKEY_QUEUE_KILL;
import static cn.whale.basic.constants.RedisConstants.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author whale.chen
 * @since 2020-11-30
 */
@Slf4j
@Service
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private KillMsgConfirmCallback killMsgConfirmCallback;

    @Override
    public List<KillCourse> selectBySessionsIds(List<Long> sessionsIds) {
        if(sessionsIds != null && sessionsIds.size() > 0){
            return baseMapper.selectBySessionsIds(sessionsIds);
        }
        return Arrays.asList();
    }

    @Override
    public List<KillCourse> listBySessionId(Long sessionId) {
        if(null == sessionId)  return Arrays.asList();

        //获取到秒杀的场次
        BoundHashOperations<String, String, KillSessions> killSessionsHashOperations = redisTemplate.boundHashOps(KEY_KILL_SESSIONS);
        KillSessions killSessions = killSessionsHashOperations.get(sessionId.toString());

        Date now = new Date();
        if(now.after(killSessions.getStartTime()) && now.before(killSessions.getEndTime())){
            //找到秒杀场次和商品的关系
            String keyRelation = String.format(KEY_KILL_COURSE_RELATION, sessionId);
            //取所有的秒杀商品的ID
            //TODO,优化get(0);
            List<Object> killCourseIds = (List<Object>) redisTemplate.boundListOps(keyRelation).range( 0, -1).get(0);
            BoundHashOperations<String, Object, KillCourse> killCourseHashOperations = redisTemplate.boundHashOps(KEY_KILL_COURSE);
            return killCourseHashOperations.multiGet(killCourseIds);
        }

        return Arrays.asList();
    }

    //1.参数判断
    //2.条件判断
    //3.预减库存
    //4.异步下单
    @Override
    //
    // @Async       //异步执行，提高并发能力
    public void kill(KillParamsDto dto) {
        Long start  = System.currentTimeMillis();
        //秒杀的用户,当前登录用户，上下文获取
        Long userId = dto.getUserId();

        //1.参数判断
        AssertUtil.assertNotNull(dto.getCourseId(),"秒杀商品不存在");
        AssertUtil.assertNotNull(dto.getKillCode(),"无效的参数【killCode】");
        //2.条件判断
        BoundHashOperations<String, String, KillCourse> killCourseBoundHashOperations = redisTemplate.boundHashOps(KEY_KILL_COURSE);
        KillCourse killCourse = killCourseBoundHashOperations.get(dto.getCourseId());
        //秒杀状态判断
        AssertUtil.assertEquals(killCourse.getStatus(),KillSessions.STATUS_ONLINE,"商品已经下线");
        //秒杀时间判断
        AssertUtil.between(killCourse.getStartTime(),killCourse.getEndTime(), DateTimeUtil.now(),"秒杀时间不对");
        //随机码判断
        AssertUtil.assertEquals(dto.getKillCode() ,killCourse.getKillCode(),"无效的参数【killCode】");
        //秒杀数量
        AssertUtil.assertEquals(dto.getKillCount(),killCourse.getKillLimit(),"秒杀数量不合法");
        //是否已经秒杀
        String userKey = String.format(KEY_KILL_USER_LOG, userId.toString());
        //AssertUtil.assertTrue(!redisTemplate.hasKey(userKey),"您已经秒杀到商品，不能重复秒杀");
        //3.预减库存
        RSemaphore semaphore = redissonClient.getSemaphore(String.format(KEY_KILL_COURSE_SEMAPHORE, dto.getCourseId()));
        AssertUtil.assertTrue(semaphore.tryAcquire(dto.getKillCount()),"秒杀失败");
        //4.记录秒杀，用来判断是否已经秒杀过了
        redisTemplate.opsForValue().setIfAbsent(String.format(KEY_KILL_USER_LOG, userId.toString()),dto.getKillCount());
        //5.异步下单

        //TODO 秒杀消息投递到MQ失败，存储一个下单失败消息到Mysql

        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(killMsgConfirmCallback);
        rabbitTemplate.setConfirmCallback(killMsgConfirmCallback);

        //发布下单消息到队列
        CourseKillTo courseKillTo = new CourseKillTo(
                dto.getOrderNo(),
                dto.getCourseId(),
                userId,
                dto.getKillCount(), //秒杀数量
                killCourse.getKillPrice().multiply(new BigDecimal(dto.getKillCount())),0);    //总额：单价*数量

        //redisTemplate.boundHashOps("KEY_KILL_RESULT").put(userId,courseKillTo);

        rabbitTemplate.convertAndSend(EXCHNAGE_ORDER_KILL,ROUTINGKEY_QUEUE_KILL,courseKillTo);
        log.info("秒杀逻辑耗时{}",System.currentTimeMillis() - start);
    }


}
